5 - sympy
Sympy er en veldig nyttig pakke innenfor samfunnsøkonomi. Med den kan vi regne analytisk, det vil si med symboler. I samfunnsøkonomi bruker vi mye matematikk som denne pakken kan hjelpe oss med. Vi starter med et eksempel på utregning av profittmaksimum:
Anta at du er bedriftsleder, og lurer på hvor mange medarbeidere du skal ansette. For en gitt mengde arbeidskraft produseres
def f(L,a):
return 60*L**a
a
definerer produktiviteten til de ansatte. Desto høyere a
er, desto mer produktive er de ansatte. Vi kan plotte denne funksjonen:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0,5,100)
plt.plot(x,f(x,0.5))
plt.xlabel('Arbeidskraft')
plt.ylabel('Produksjon')
Som vi ser er produktiviteten avtakende, kurven stiger mindre utover i diagrammet. Det er fordi a
er mindre enn én. (Forsøk med andre tall for a
!)
Når produktfunksjonen er definert, kan vi definere fortjenesten til bedriften, eller "profittfunksjonen" som vi gjerne kaller det i samfunnsøkonomi. La oss si at bedriften betaler hver arbeider w
tusen kroner, og at faste kostnader er K
.
Fortenesten er pris ganger solgt mengde, p*f(L,a)
og kostnadene er lønnskostnader w*L
og faste utgifter K
. Profittfunksjonen blir da
def profit(L,a,w,p,K):
return p*f(L,a)-w*L-K
La oss se på den grafisk. Dersom prisen per enhet er hundre kroner, lønna er 2 500 per dag og bedriften har 300 000 i faste utgifter per dag, så ser profittfunksjonen slik ut:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.pyplot as plt
import numpy as np
#creating the plot
x = np.linspace(0,5000,100)
fig,ax=plt.subplots()
ax.set_ylabel('kroner')
ax.set_xlabel('Antall ansatte')
#plotting the function
plt.plot(x,profit(x,0.9,2500,100,300000),label='Fortjeneste')
ax.legend(loc='lower right',frameon=False)
Vi bruker metoden med fig,ax=plt.subplots()
, siden vi skal bygge på denne grafen.
Vi skal nå begynne å bruke sympy. Det første vi må gjøre er å definere hvilke symboler som skal behandles analytisk (altså som symboler og ikke flyttall). Det gjør vi slik:
import sympy as sp
L,a,w,p,K=sp.symbols("L a w p K")
Med symbolene definert, vil nå vår profittfunksjon vises analytisk når vi bruker de definere symbolene:
profit(L,a,w,p,K)
Vi ønsker å finne ut for hvilken arbeidskraft fortjenesten er størst. Det er det høyeste punktet i figuren fra Eksempel 3. På dette punktet har profittfunksjonen ingen stigning, slik at den deriverte er null. For å finne dette punktet må vi først finne den deriverte. Det gjør vi i sympy med funksjonen diff()
. Den tar to argumenter. Det første er funksjonen, det andre er den variabel vi ønsker å derivere med hensyn til.
Som vi ser av figuren i Eksempel 3, så har vi arbeidskraft L
langs x-aksen, så det er denne variabelen vi ønsker å derivere med hensyn til. Den deriverte av profitt()
med hensyn til L
er dermed:
d_profitt=sp.diff(profit(L,a,w,p,K),L)
d_profitt
For å finne punktet der denne deriverte er null, setter vi opp en ligning der den deriverte er null, og løser for den L
som tilfredstiller ligningen. En slik ligning kalles "førsteordensbetingelse", eller "first order condition" på engelsk. Vi forkorter den derfor til "foc":
foc=sp.Eq(d_profitt,0)
foc
Vi kan nå løse førsteordensbetingelsen med funksjonen solve
, som ligger i modulen solvers
i sympy:
from sympy.solvers import solve
L_max=solve(foc,L)[0]
L_max
Legg merke til at resultatet ligger i en liste med lengde 1, så vi må hente ut element 0 i listen for å vise resultatet. Vi kan finne hva den analytiske verdien er i maksimum ved å sette L_max
inn i profittfunksjonen:
profit_max=profit(L_max,a,w,p,K)
profit_max
Vi kan nå beregne de nummeriske verdiene ved å sette inn noen passende tall for de ukjente symbolene. Vi prøver med 0.5 for produktivitet a
, 0.3 for lønn w
, 1 for pris p
og 1 for kapital K
. Vi forteller sympy om at vi ønsker å bruke dise verdiene ved å lage et oppslag der hvert av symbolene er nøkkel til hver av de nummeriske verdiene:
num_dict={a:0.9,w:2500,p:100,K:300000}
num_dict
Vi kan nå finne nummerisk hvor mye arbeidskraft som trengs for å oppnå maksimal fortjeneste:
L_max.subs(num_dict)
Om vi nå legger dette tallet inn for symbol L
, kan vi finne hvor stor fortjenesten er i dette punktet. Vi starter med å legge inn verdien for L som gir maksimal fortjeneste:
num_dict[L]=L_max.subs(num_dict)
num_dict
Med det oppdaterte oppslaget blir fortjenesten
profit_max_num=float(profit(L,a,w,p,K).subs(num_dict))
profit_max_num
Med modulene display
og Markdown
fra IPython
(pakken som driver Jupyter), kan vi sette dette in i en pen tabell:
from IPython.display import Markdown
tbl=f"""
| | Desimalverdi | Analytisk verdi |
| :-------------------------------| :----------------------------------------| :-----------------------------|
| Optimal mengde arbeidskraft: | ${np.round(float(num_dict[L]),1)}$ |${sp.latex(L_max)}$ |
| Maksimal profitt | ${np.round(float(profit_max_num),1)}$ |${sp.latex(profit_max)}$ |
"""
display(Markdown(tbl))
La oss nå plotte løstningen. Vi plotter her følgende, i rekkefølge:
Legg merke til at vi legger inn verdiene vi har definert i num_dict
inn i profittfunksjonen. Generelt er det en god idé i programmering å "hardkode" tall minst mulig. Definer det heller tallet som en variabel eller element i en dict
og referer til det senere.
#Plotting the tangent
ax.plot(x,
profit_max_num*np.ones(len(x)),
label='tangent')
#Plotting the vertical line
ax.vlines(float(L_max.subs(num_dict)), #x-value
-300000, #min y-value
profit_max_num*1.1, #max y-value
#formatting:
colors=['black'],
linestyles='dashed',
label='optimum')
ax.legend(loc='lower right',frameon=False)
ax.axhline(0, color='black',lw=0.1)
fig
I forelesning 3 definerte vi disse tilbuds og etterspørselsfunksjonene:
def supply(x):
return (x**2)*(1/250)
def demand(x):
return 3000/(100+x)
Og vi tegnet dem slik:
#drawing 100 points in the interval 0.0 to 100
q = np.linspace(1,100,100)
#creating the plot
fig,ax=plt.subplots()
ax.set_ylabel('Pris')
ax.set_xlabel('Enheter')
#drawing supply
ax.plot(q,supply(q),label='Tilbud')
#drawing demand
ax.plot(q,demand(q),color='green',label='Etterspørsel')
#adding legend:
ax.legend(loc='upper center',frameon=False)
Vi løste da likevekten grafisk, ved å se sånn cirka hvor tilbud er lik etterspørsel. Med sympy kan vi la python regne ut dette, og konsument og produsentoverskudd. Vi gjør dette ved å definere mengde x
som en eksogen variabel, og sette opp ligningen vi trenger, altså at tilbud skal være lik etterspørsel:
x=sp.symbols('x')
eq_cond=sp.Eq(demand(x),supply(x))
eq_cond
Vi kan nå løse dette med solve fra sympy, som i forrige eksempel:
x_eq=solve(eq_cond,x)
x_eq
Bare én av disse løsningene er gyldige. De to siste i listen x_eq
er såkalte "imaginære tall", det ser vi av I
'en. Vi går ikke lenger inn på hva dette er her, men nøyer oss med å si at en likevekt ikke kan være et imaginært tall. Løsningen er altså x_eq[0]
. Vi kan sette denne inn i enten tilbuds eller etterspørselfunksjonen for å få likevektsprisen
p_eq=demand(x_eq[0])
print(f"""
Likevektspris er {p_eq}
Likevektskvantum er {x_eq[0]}
""")
Etterspørselskurven kan ses på som en rekke med konsumenter med ulik betalingsvilje i fallende rekkefølge. Alle konsumentene som betaler p_eq
har dermed et overskudd som er lik differansen mellom p_eq
og konsumentens punkt på etterspørselskurven. Summen av alle konsumentenes overskudd kalles konsumentoverskuddet. Dette kan illustreres ved å legge et skravert område til figuren over
q = np.linspace(0,float(x_eq[0]),100)
ax.fill_between(q, #x-values
float(p_eq), #Eqilibrium price
demand(q), #y-values
#formatting:
color = "pink",alpha = 0.3,label='Konsumentoverskudd')
ax.legend(loc='upper center',frameon=False)
fig
Akkrat som at vi kan regnet ut skjæringspunktet med sympy, så kan vi regne ut det skraverte konsumentoverskuddet. Vi bruker da det vi har lært i matematikkurset; arealet under en funksjon er integralet til funksjonen. Vi skal finne arealet under etterspørselsfunksjonen demand(x)
, men kun ned til prisen p_eq
, så vi integrer differansen demand(x)-p_eq
. Dette gjør vi for alle omsatte enheter, altså frem til omsatt kvantum x_eq[0]
.
Vi skal altså integrere demand(x)-p_eq
i intervalet 0 til x_eq[0]
. Det kan vi gjøre i sympy slik:
consumer_surplus=sp.integrate(demand(x)-float(p_eq),(x,0,x_eq[0]))
consumer_surplus
På samme måte er produsentoverskuddet arealet over tilbuskurven, opp til prisen, altså det gule området i figuren under
ax.fill_between(q,supply(q),float(p_eq), color = "yellow",alpha = 0.3,label='Produsentoverskudd')
ax.vlines(float(x_eq[0]), 0, 25,colors=['black'],linestyles='dashed', label='x_eq[0]')
ax.legend(loc='upper center',frameon=False)
fig
Vi kan regne ut dette område også, som altså er integralet av differansen mellom prisen og tilbudskruven, frem til x_eq[0]
.
producer_surplus=sp.integrate(p_eq-supply(x),(x,0,x_eq[0]))
producer_surplus
Summen av produsentoverskuddet og konsumentoversdkuddet kalles "velferdsgevinsten". Vi kan finne den ved å legge sammen konsument- og produsentoverskudd:
producer_surplus+consumer_surplus
Eller ved å ta integralet av differansen mellom etterspørsel og tilbud:
welfare_surplus=sp.integrate(demand(x)-supply(x),(x,0,x_eq[0]))
welfare_surplus
Vi kan nå lage en tabell som oppsumerer resultatene:
tbl=f"""
| | Verdi |
| :-------------------| :----------------------------------------|
| Solgt mengde: | ${np.round(float(x_eq[0]),1)}$ |
| Pris: | ${np.round(float(p_eq),1)}$ |
| Verdiskapning/<br>velferdsgevinst: | ${np.round(float(welfare_surplus),1)}$ |
| Konsumentoverskudd: | ${np.round(float(consumer_surplus),1)}$ |
| Produsentoverskudd: | ${np.round(float(producer_surplus),1)}$ |
"""
display(Markdown(tbl))
I eksempel 20 hadde vi én ligning med én ukjent. Det er imidlertid enkelt å løse flere ligninger med flere ukjente. Forskjellen er bare at når det er flere ligninger, så setter vi hver av disse inn i en liste. Som dere vet må det være akkurat like mange ukjente som ligninger, så vi må også sette akkurat like mange variabler inn i en annen liste. Disse to listene utgjør så argumentene i solve()
funksjonen. La oss ta et eksempel. Vi begynner med å definere to lister med henholdsvis tre variabler og tre ligninger:
x,y,z=sp.symbols("x y z")
symbols=[x,y,z]
equations=[
sp.Eq(2*x+8*y+3*z,7),
sp.Eq(-3*x+15*y-14*z,-20),
sp.Eq(11*x-6*y+7*z,35)
]
Vi setter så disse to listene inn i solve()
akkurat som om det var én ligning og én ukjent, og finner svaret:
solve(equations,symbols)
Her skal dere løse matematikkoppgaver med Sympy, og bruke fordelene som programmering gir til å løse oppgavene så effektivt som mulig. I stedet for å løse hver oppgave individuelt, skal dere derfor lage funksjoner som tar uttrykkene som argumenter, og så løse hver oppgave.
Lag en funksjon test_solve(eqs)
der eqs er et Sympy ligningssett, og der du bruker solve(f)
til å finne løsning på likhetene. Lag en test i funksjonen, ved å sette løsningen inn i likheten.
Funksjonen skal returnere både svaret og resultatet av løsningen.
from sympy.solvers import solve
import sympy as sp
x,y,z=sp.symbols("x y z")
Løs alle oppgavene med Sympy i én celle. Bruk diff()
-funksjonen for å derivere, og gjør koden så kort som mulig (i antall tegn, ikke antall linjer).
Forenkl uttrykkene i Oppgavene 1 a)-d) med færrest mulig tegn. Kommenter hvilke av oppgavene som lar seg forenkle.
Lag to funksjoner f_deriv_prod(f, g)
og f_deriv_frac(f, g)
som bruker henholdsvis produktregelen og brøkregelen til å derivere et uttrykk. Argumenter skal være de to delene i produktet.
Om produktet for eksempel er $\sqrt{3x-1}\cdot(1+x)$ skal argumentene være $\sqrt{3x-1}$ og $(1+x)$. Funksjonen skal returnere den deriverte av produktet.
Deriver alle utrykene i e)-m) med disse funksjonene, men velg først ut alle produktene og deriver de, før du i neste omgang deriverer brøkene.
Test at produktregelen fungerer ved å printe differansen mellom funksjonene over og derivering av hele uttrykket med sp.diff()
. Du kan sette dette inn i koden over om du vil.
Lag en funksjon f_deriv_chain(f, g)
som bruker kjerneregelen til å derivere et uttrykk av typen $f(g(x))$.
Har du for eksempel et uttrykk $\sqrt{3x-1}$, så er $f(y)=\sqrt{y}$ og $g(x)=\sqrt{3x-1}$. Funksjonen skal da returnere $f'(g(x))\cdot g'(x)=\frac{1}{2}\frac{1}{\sqrt{3x-1}}\cdot 3$
Husk å bytt ut $y$ med $g(x)$ (i dette tilfelle $3x-1$) før svaret returneres.
a) Bruk f_deriv_chain(f, g)
på uttrykkene i Oppgave 1 a), b), d), e) og Oppgave 2 a) og e).
b) Bruk f_deriv_prod(f, g)
i kombinasjon med f_deriv_chain(f(y), g(x))
og løs Oppgave 1 h) og Oppgave 2 b)
Test samtidig resultatene med sp.diff())
, der du stter inn kjernen i funksjonen før du deriverer.
Løs følgende oppgaver: Oppgave 1 a), b), f) og Oppgave 2 a) og b). Lag koden med så få tegn som mulig-